package falcon.common;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;

import sun.security.util.Password;

/**
 * <pre>
 * <b>local bash</b>
 * ProcessBuilder
 * <b>remote bash</b>
 * Jsch
 * http://www.jcraft.com/jsch/examples/Exec.java.html
 * </pre>
 */
public final class Bash {
	
	public Bash(String command) {
		m_mode = Bash.Mode.Local;
		m_command = command;
		m_result = null;
	}
	
	public Bash(String command, String host, int port, String user, String password) {
		m_mode = Bash.Mode.Remote;
		m_command = command;
		m_host = host;
		m_port = port;
		m_user = user;
		m_password = password;
		m_result = null;
	}
	
	public void bindOutput(StringBuilder result) {
		m_result = result;
	}
	
	public int execute() {
		if (m_mode == Bash.Mode.Remote) {
			return executeRemoteBash();
		}
		return executeLocalBash();
	}
	
	private int executeLocalBash() {
		int errno = Errno.SUCCESS;
		ProcessBuilder builder = null;
		Process process = null;
		try {
			try {
				builder = new ProcessBuilder(new String[] {"/bin/sh", "-c", m_command});
				builder.redirectErrorStream(true);
				process = builder.start();
				InputStream in = process.getInputStream();
				byte[] array = new byte[1024];
				while (true) {
					int remain = in.read(array, 0, array.length);
					if (remain < 0) {
						break;
					}
					if (m_result != null) {
						m_result.append(new String(array, 0, remain));
					}
				}
				errno = process.waitFor();
			} finally {
				if (process != null) {
					process.destroy();
				}
			}
		} catch (Exception e) {
			errno = Errno.COM_CATCH_EXCEPTION;
			Log.log(e, "command=%s", m_command);
		}
		return errno;
	}
	
	private int executeRemoteBash() {
		final int TIMEOUT = 2000;
		int errno = Errno.SUCCESS;
		ChannelExec channel = null;
		Session session = null;
		
		try {
			ByteArrayOutputStream out = new ByteArrayOutputStream(256);
			JSch jsch = new JSch();
			session = jsch.getSession(m_user, m_host, m_port);
			session.setPassword(m_password);
			session.setUserInfo(new MyUserInfo());
			session.connect(TIMEOUT);
			channel = (ChannelExec) session.openChannel("exec");
			channel.setCommand(m_command);
			channel.setInputStream(null);
			// 绑定错误流
			channel.setOutputStream(out, true);
			channel.setErrStream(out, true);
			channel.connect(TIMEOUT);
			byte[] array = new byte[1024];
			InputStream in = channel.getInputStream();
			while (true) {
				while (in.available() > 0) {
					int remain = in.read(array, 0, array.length);
					if (remain < 0) {
						break;
					}
					if (m_result != null) {
						m_result.append(new String(array, 0, remain));
					}
				}
				if (channel.isClosed() && in.available() <= 0) {
					break;
				}
				Thread.sleep(100L);
			}
			// 读取错误流信息
			array = out.toByteArray();
			if (array.length > 0 && m_result != null) {
				m_result.append(new String(array));
			}
		} catch (Exception e) {
			errno = Errno.COM_CATCH_EXCEPTION;
			Log.log(e, "host=%s;port=%d;user=%s;command=%s", m_host, m_port, m_user, m_command);
		} finally {
			if (channel != null) {
				if (errno == Errno.SUCCESS) {
					errno = channel.getExitStatus();
				}
				channel.disconnect();
			}
			if (session != null && session.isConnected()) {
				session.disconnect();
			}
		}
		return errno;
	}
	
	private enum Mode {
		Local,
		Remote,
	}
	
	private class MyUserInfo implements UserInfo {
		
		@Override
		public void showMessage(String message) {
			
		}
		
		@Override
		public boolean promptYesNo(String message) {
			return true;
		}
		
		@Override
		public boolean promptPassword(String message) {
			return false;
		}
		
		@Override
		public boolean promptPassphrase(String message) {
			return false;
		}
		
		@Override
		public String getPassword() {
			return null;
		}
		
		@Override
		public String getPassphrase() {
			return null;
		}
	}

	private Bash.Mode m_mode;
	private String m_command;
	private String m_host;
	private int m_port;
	private String m_user;
	private String m_password;
	private StringBuilder m_result;
}
